﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using RaisingForce.Raise.Hardware;
using RaisingForce.Raise.Measures;
using RaisingForce.Raise.Presentation;
using RaisingForce.Raise.Presentation.Names;
using RaisingForce.Raise.Presentation.Rules;
using RaisingForce.Raise.Presentation.ViewModel;
using RaisingForce.Raise.Presentation.ViewModel.Extensions;
using RaisingForce.Raise.Presentation.ViewModel.Specialized;
using RaisingForce.RaiseHardwareTest.Properties;

namespace RaisingForce.RaiseHardwareTest.ViewModel.Extensions
{
    internal static class SettingsViewModelExtensions
    {
        #region CreateInputControls Method Declaration

        public delegate SettingViewModel CreateControlFunc(
            SettingsViewModel viewModel,
            int labelMinWidth);

        public static readonly ReadOnlyCollection<CreateControlFunc> CreateInputControlFunctions =
            new List<CreateControlFunc>
            {
                CreateComPortControl,
                (viewModel, labelMinWidth) =>
                    CreateObstacleDistanceControl(
                        viewModel, 
                        ApplicationHelper.DistanceValueMaxDigits, 
                        labelMinWidth),
                (viewModel, labelMinWidth) =>
                    CreateWheelRunDistanceControl(
                        viewModel, 
                        ApplicationHelper.DistanceValueMaxDigits, 
                        labelMinWidth),
                CreateLeftTurnTachoControl,
                CreateRightTurnTachoControl,
                CreateSupposedPowerControl,
                CreateWheelBalanceControl,
                CreateObstacleDetectionTimeControl,
                CreateWheelRunTimeControl,
                CreateLeftTurnTimeControl,
                CreateRightTurnTimeControl
            }
            .AsReadOnly();

        public static IList<SettingViewModel> GetInputControls(
            this SettingsViewModel viewModel,
            int labelMinWidth,
            IEnumerable<CreateControlFunc> createControlFunctions)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Requires(createControlFunctions != null);
            Contract.Requires(Contract.ForAll(createControlFunctions, func => func != null));
            Contract.Ensures(Contract.Result<IList<SettingViewModel>>() != null);
            Contract.Ensures(Contract.Result<IList<SettingViewModel>>().Count == createControlFunctions.Count());
            return createControlFunctions
                .Select(func => func(viewModel, labelMinWidth))
                .ToArray();
        }

        public static ObservableCollection<SettingViewModel> CreateInputControls(
            this SettingsViewModel viewModel,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<ObservableCollection<SettingViewModel>>() != null);
            return new ObservableCollection<SettingViewModel>(
                GetInputControls(viewModel, labelMinWidth, CreateInputControlFunctions));
        }

        #endregion

        #region Input Controls Creators

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateComPortControl(
            this SettingsViewModel viewModel,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new ComPortSettingBoxViewModel(
                Resources.Settings_ComPort_Invalid,
                Resources.Settings_ComPort_Empty,
                value => viewModel.Options.ComPort = value,
                () => viewModel.Options.ComPort.ToString())
            {
                LabelText = Resources.Settings_ComPort_Label,
                LabelMinWidth = labelMinWidth,
                MeasureText = viewModel.ComPortMeasureText
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateObstacleDistanceControl(
            this SettingsViewModel viewModel,
            int valueMaxDigits,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(valueMaxDigits >= 0);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new DoubleMeasureSettingBoxViewModel<DistanceRule, IDistanceMeasure>(
                Resources.Settings_ObstacleDistance_Invalid,
                Resources.Settings_ObstacleDistance_Empty,
                viewModel.UsingMeasures.DistanceMeasure,
                value => viewModel.Options.ObstacleDistance = viewModel.UsingMeasures.GetDistance(value),
                () => ApplicationHelper
                    .GetRoundedDistanceValue(
                        viewModel.GetDistanceValue(x => x.ObstacleDistance),
                        valueMaxDigits)
                    .ToString())
            {
                LabelText = Resources.Settings_ObstacleDistance_Label,
                LabelMinWidth = labelMinWidth,
                MeasureText = viewModel.UsingMeasures.DistanceMeasureText
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateWheelRunDistanceControl(
            this SettingsViewModel viewModel,
            int valueMaxDigits,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(valueMaxDigits >= 0);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new DoubleMeasureSettingBoxViewModel<DistanceRule, IDistanceMeasure>(
                Resources.Settings_WheelRunDistance_Invalid,
                Resources.Settings_WheelRunDistance_Empty,
                viewModel.UsingMeasures.DistanceMeasure,
                value => viewModel.Options.WheelRunDistance = viewModel.UsingMeasures.GetDistance(value),
                () => ApplicationHelper
                    .GetRoundedDistanceValue(
                        viewModel.GetDistanceValue(x => x.WheelRunDistance),
                        valueMaxDigits)
                    .ToString())
            {
                LabelText = Resources.Settings_WheelRunDistance_Label,
                LabelMinWidth = labelMinWidth,
                MeasureText = viewModel.UsingMeasures.DistanceMeasureText
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateLeftTurnTachoControl(
            this SettingsViewModel viewModel,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new TachoSettingBoxViewModel(
                Resources.Settings_LeftTurnTacho_Invalid,
                Resources.Settings_LeftTurnTacho_Empty,
                value => viewModel.Options.LeftTurnTacho = value,
                () => viewModel.Options.LeftTurnTacho.ToString())
            {
                LabelText = Resources.Settings_LeftTurnTacho_Label,
                LabelMinWidth = labelMinWidth,
                MeasureText = viewModel.TachoMeasureText
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateRightTurnTachoControl(
            this SettingsViewModel viewModel,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new TachoSettingBoxViewModel(
                Resources.Settings_RightTurnTacho_Invalid,
                Resources.Settings_RightTurnTacho_Empty,
                value => viewModel.Options.RightTurnTacho = value,
                () => viewModel.Options.RightTurnTacho.ToString())
            {
                LabelText = Resources.Settings_RightTurnTacho_Label,
                LabelMinWidth = labelMinWidth,
                MeasureText = viewModel.TachoMeasureText
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateSupposedPowerControl(
            this SettingsViewModel viewModel,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new SliderSettingViewModel(
                value => viewModel.Options.SupposedPower = (sbyte)value,
                () => viewModel.Options.SupposedPower)
            {
                LabelText = Resources.Settings_SupposedPower_Label,
                LabelMinWidth = labelMinWidth,
                Minimum = 5,
                Maximum = 100,
                ValueMultiplier = 1,
                ValueStep = 5,
                ValueFormat = "{0:F0}%"
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateWheelBalanceControl(
            this SettingsViewModel viewModel,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new SliderSettingViewModel(
                value => viewModel.Options.WheelBalance = (float)value,
                () => viewModel.Options.WheelBalance)
            {
                LabelText = Resources.Settings_WheelBalance_Label,
                LabelMinWidth = labelMinWidth,
                Minimum = 0.5,
                Maximum = 2.0,
                ValueMultiplier = 100,
                ValueStep = 0.05,
                ValueFormat = "{0:F0}%"
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateObstacleDetectionTimeControl(
            this SettingsViewModel viewModel,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new DoubleMeasureSettingBoxViewModel<TimeRule, ITimeMeasure>(
                Resources.Settings_ObstacleDetectionTime_Invalid,
                Resources.Settings_ObstacleDetectionTime_Empty,
                viewModel.UsingMeasures.TimeMeasure,
                value => viewModel.Options.ObstacleDetectionTime = viewModel.UsingMeasures.GetTime(value),
                () => viewModel.GetTimeValue(x => x.ObstacleDetectionTime).ToString())
            {
                LabelText = Resources.Settings_ObstacleDetectionTime_Label,
                LabelMinWidth = labelMinWidth,
                MeasureText = viewModel.UsingMeasures.TimeMeasureText
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateWheelRunTimeControl(
            this SettingsViewModel viewModel,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new DoubleMeasureSettingBoxViewModel<TimeRule, ITimeMeasure>(
                Resources.Settings_WheelRunTime_Invalid,
                Resources.Settings_WheelRunTime_Empty,
                viewModel.UsingMeasures.TimeMeasure,
                value => viewModel.Options.WheelRunTime = viewModel.UsingMeasures.GetTime(value),
                () => viewModel.GetTimeValue(x => x.WheelRunTime).ToString())
            {
                LabelText = Resources.Settings_WheelRunTime_Label,
                LabelMinWidth = labelMinWidth,
                MeasureText = viewModel.UsingMeasures.TimeMeasureText
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateLeftTurnTimeControl(
            this SettingsViewModel viewModel,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new DoubleMeasureSettingBoxViewModel<TimeRule, ITimeMeasure>(
                Resources.Settings_LeftTurnTime_Invalid,
                Resources.Settings_LeftTurnTime_Empty,
                viewModel.UsingMeasures.TimeMeasure,
                value => viewModel.Options.LeftTurnTime = viewModel.UsingMeasures.GetTime(value),
                () => viewModel.GetTimeValue(x => x.LeftTurnTime).ToString())
            {
                LabelText = Resources.Settings_LeftTurnTime_Label,
                LabelMinWidth = labelMinWidth,
                MeasureText = viewModel.UsingMeasures.TimeMeasureText
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateRightTurnTimeControl(
            this SettingsViewModel viewModel,
            int labelMinWidth)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new DoubleMeasureSettingBoxViewModel<TimeRule, ITimeMeasure>(
                Resources.Settings_RightTurnTime_Invalid,
                Resources.Settings_RightTurnTime_Empty,
                viewModel.UsingMeasures.TimeMeasure,
                value => viewModel.Options.RightTurnTime = viewModel.UsingMeasures.GetTime(value),
                () => viewModel.GetTimeValue(x => x.RightTurnTime).ToString())
            {
                LabelText = Resources.Settings_RightTurnTime_Label,
                LabelMinWidth = labelMinWidth,
                MeasureText = viewModel.UsingMeasures.TimeMeasureText
            };
        }

        #endregion

        #region CreateCommands Method Declaration

        public delegate CommandViewModel CreateCommandFunc(
            SettingsViewModel viewModel);

        public static readonly ReadOnlyCollection<CreateCommandFunc> CreateCommandFunctions =
            new CreateCommandFunc[]
            {
                CreateDefaultsCommand
            }
            .ToList()
            .AsReadOnly();

        public static IList<CommandViewModel> GetCommands(
            this SettingsViewModel viewModel,
            IEnumerable<CreateCommandFunc> createCommandFunctions)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(createCommandFunctions != null);
            Contract.Requires(Contract.ForAll(createCommandFunctions, func => func != null));
            Contract.Ensures(Contract.Result<IList<CommandViewModel>>() != null);
            Contract.Ensures(Contract.Result<IList<CommandViewModel>>().Count == createCommandFunctions.Count());
            return createCommandFunctions
                .Select(func => func(viewModel))
                .ToArray();
        }

        public static ObservableCollection<CommandViewModel> CreateCommands(
            this SettingsViewModel viewModel)
        {
            Contract.Requires(viewModel != null);
            Contract.Ensures(Contract.Result<ObservableCollection<CommandViewModel>>() != null);
            return new ObservableCollection<CommandViewModel>(
                GetCommands(viewModel, CreateCommandFunctions));
        }

        #endregion

        #region Command Creators

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static CommandViewModel CreateDefaultsCommand(
            this SettingsViewModel viewModel)
        {
            Contract.Requires(viewModel != null);
            Contract.Ensures(Contract.Result<CommandViewModel>() != null);
            return new CommandViewModel(
                ButtonNames.Defaults,
                new DelegateCommand(p =>
                    {
                        viewModel.AskConfirmation(
                            Resources.Settings_Defaults_Confirmation,
                            viewModel.DisplayName,
                            okAction: () => viewModel.Options =
                                new RobotHardwareOptions(RobotHardwareOptions.Default));
                    }));
        }

        #endregion

        #region GetErrors Static Method

        public static string GetErrors(this SettingsViewModel viewModel)
        {
            Contract.Requires(viewModel != null);
            Contract.Ensures(Contract.Result<string>() != null);
            return viewModel.InputControls
                .Select(control => control.GetFirstValidationError())
                .Where(error => error != null)
                .Aggregate(
                    new StringBuilder(),
                    (builder, error) =>
                    {
                        if (builder.Length > 0)
                        {
                            builder.AppendLine();
                        }
                        return builder.Append(error);
                    })
                .ToString();
        }

        #endregion
    }
}
